/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bakenote.components;

import bakenote.core.Amount;
import bakenote.core.Formula;
import bakenote.core.Ingredient;
import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.wtk.*;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.Insets;
import org.apache.pivot.wtk.Label;
import org.apache.pivot.wtk.content.TableViewNumberCellRenderer;
import org.apache.pivot.wtk.content.TableViewRowEditor;
import org.apache.pivot.wtk.skin.CardPaneSkin.SelectionChangeEffect;
import org.apache.pivot.wtk.validation.DoubleValidator;

import java.awt.*;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author lello
 */
public class FormulaTablePane extends TablePane {

	private BoxPane formulaBoxPane = null;
	private TableView formulaTableView = null;
	private BoxPane amountBoxPane = null;
	private Label amountLabelText = new Label("Total:");
	private Label amountDataLabel = null;

	public FormulaTablePane(final Formula formula) {

		createFormulaTableView(formula);

		// Create formulaBox
		formulaBoxPane = new BoxPane();
		formulaBoxPane.setOrientation(Orientation.VERTICAL);
		formulaBoxPane.getStyles().put("fill", true);
		formulaBoxPane.getStyles().put("horizontalAlignment", HorizontalAlignment.CENTER);
		formulaBoxPane.add(formulaTableView);

		// create amount box
		createAmountLabel(formula);

		// Apply styles to the tablePane
		getStyles().put("padding", 4);
		TablePane.ColumnSequence cols = this.getColumns();
		cols.add(new TablePane.Column());
		cols.get(0).setWidth("1*");
		TablePane.Row formulaPaneRow = new TablePane.Row();
		TablePane.Row amountRow = new TablePane.Row();
		formulaPaneRow.add(formulaBoxPane);
		amountRow.add(amountBoxPane);
		TablePane.RowSequence rows = this.getRows();
		rows.add(formulaPaneRow);
		rows.add(amountRow);

	}

	private void createFormulaTableView(final Formula formula) {

		formulaTableView = new TableView();

		formulaTableView.getTableViewRowListeners().add(new TableViewRowListener.Adapter() {

			@Override
			public void rowUpdated(TableView tv, int index) {
				Ingredient ing = (Ingredient) tv.getTableData().get(index);
				if (ing.getAmount().isMeasurable()) {
					double w = ing.getAmount().getDouble();
					double p = ing.getPercent();
					double wf = w / p;
					//System.out.println("w p wf "+ w+ " "+p+" "+wf);
					for (Ingredient in : formula.getIngredients()) {
						if (in.getAmount().isMeasurable()) {
							double pi = in.getPercent();
							in.setAmount(pi * wf);
						}
						//((Ingredient) tv.getTableData().get(i)).setAmount(pi * wf);
					}
				}
				amountDataLabel.load(formula);
			}
		});

		TableView.ColumnSequence columns = formulaTableView.getColumns();
		columns.add(new TableView.Column("Name"));
		columns.add(new TableView.Column("Amount"));
		columns.add(new TableView.Column("Percent"));
		columns.get(0).setWidth("5*");
		columns.get(1).setWidth("2*");
		columns.get(2).setWidth("2*");
		// set renderers
		BakerPercentRenderer percentRenderer = new BakerPercentRenderer();
		AmountCellRenderer amountRenderer = new AmountCellRenderer();
		columns.get(2).setCellRenderer(percentRenderer);
		columns.get(1).setCellRenderer(amountRenderer);

		// set TableView properties
		formulaTableView.getStyles().put("font", new Font("Verdana", Font.PLAIN, 12));
		formulaTableView.setTableDataKey("ingredients");
		formulaTableView.load(formula);

		// Install a row editor on each table to edit the amount of each ingredient
		//formulaTableView.setRowEditor(new FormulaRowEditor(formulaTableView));
        formulaTableView.setRowEditor(new FormulaRowEditor());

	}

	private void createAmountLabel(final Formula formula) {

		amountBoxPane = new BoxPane();
		amountBoxPane.setOrientation(Orientation.HORIZONTAL);
		amountBoxPane.getStyles().put("fill", true);
		amountBoxPane.getStyles().put("horizontalAlignment", HorizontalAlignment.LEFT);
		amountBoxPane.getStyles().put("verticalAlignment", VerticalAlignment.CENTER);
		amountBoxPane.getStyles().put("padding", new Insets(10,2,10,2));
        amountBoxPane.getStyles().put("backgroundColor",11);
		amountLabelText.getStyles().put("font", new Font("Verdana", Font.PLAIN, 12));
		amountDataLabel = new Label();
		amountDataLabel.getStyles().put("font", new Font("Verdana", Font.BOLD, 12));
		amountDataLabel.setTextKey("amount");
		amountDataLabel.setTextBindMapping(new FormulaAmountLabelMapping());
		amountDataLabel.load(formula);
		amountDataLabel.setPreferredWidth(80);
		amountBoxPane.add(amountLabelText);
		amountBoxPane.add(amountDataLabel);

	}

	public Label getAmountLabel() {
		return amountDataLabel;
	}

	public TableView getTableView() {
		return formulaTableView;
	}

	private static class BakerPercentRenderer extends TableViewNumberCellRenderer {

		private static NumberFormat nf = null;

		public BakerPercentRenderer() {
			nf = NumberFormat.getPercentInstance();
			nf.setMaximumFractionDigits(2);
			this.setNumberFormat(nf);
		}

		@Override
		public String toString(Object row, String columnName) {
			Ingredient ing = (Ingredient) row;
			if (!ing.getAmount().isMeasurable()) {
				return "-";
			} else {
				return super.toString(row, columnName);
			}
		}
	}

	private static class AmountCellRenderer extends TableViewNumberCellRenderer {

		private static DecimalFormat nfi = new DecimalFormat("#.#");

		public AmountCellRenderer() {
			//nfi.setMaximumFractionDigits(1);
			//nfi.setMinimumFractionDigits(1);
			//((DecimalFormat) nfi).setDecimalSeparatorAlwaysShown(true);
			this.setNumberFormat(nfi);
		}

		@Override
		public String toString(Object row, String columnName) {
			Ingredient ing = (Ingredient) row;
			Amount amount = ing.getAmount();
			//System.out.println("unit "+unit+" amount "+amount);
			if (amount.isMeasurable()) {
				//System.out.println("measurable amount.. "+nfi.format(amount.getDouble()));
				//return super.toString(row, columnName);
				return nfi.format(amount.getDouble()) + " " + ing.getUnit();
			} else {
				if (!ing.getAmount().isNull()) {
					return ing.getAmount().toString();
				} else {
					//System.out.println("render number cell");
					//return super.toString(row, columnName) + " " + unit;
					//return nfi.format(amount);
					return "";
				}
			}
		}
	}

	private static class AmountValidator extends DoubleValidator {

		private double min, max;

		/**
		public AmountValidator(Locale locale, double min, double max) {
		super(locale);
		this.min = min;
		this.max = max;
		}**/
		public AmountValidator(double min, double max) {
			this.min = min;
			this.max = max;
		}

		@Override
		public boolean isValid(String text) {

			boolean valid = false;
			boolean tempValid = false;

			//System.out.println("is text valid? " + text);
			if (text.equals("t.t.") || text.equals("q.b.")) {
				valid = true;
			} else {
				// Here we force d to be > than min. The DoubleRangeValidator use
				// the condition d >= min
				tempValid = super.isValid(text);
				if (tempValid) {
					try {
						//valid = valid && (parseNumber(text).doubleValue() > min);
						valid = tempValid && (NumberFormat.getInstance().parse(text).doubleValue() > min);
						//valid = valid && (DecimalFormat.getInstance().parse(text).doubleValue() > min);
					} catch (ParseException ex) {
						valid = false;
						//Logger.getLogger(FormulaPane.class.getName()).log(Level.SEVERE, null, ex);
					}
				} 
			}
			return valid;

		}
	}

	private static class AmountTextInputMapping implements TextInput.TextBindMapping {

		private static DecimalFormat nf = new DecimalFormat("#.#");
		private static DecimalFormat amountFormat = new DecimalFormat("#.#");
		private TextInput ti;

		public AmountTextInputMapping(TextInput ti) {
			this.ti = ti;
		}

		@Override
		public String toString(Object value) {
			//System.out.println("amount mapping toString" + value);
			//amountFormat = NumberFormat.getInstance(Locale.ITALIAN);
			//int index = tv.getSelectedIndex();
			//System.out.println("index table "+index);
			//if (!((Ingredient)tv.getTableData().get(index)).getAmount().isMeasurable()) {
			Amount am = (Amount) value;
			if (am.isMeasurable()) {
				//System.out.println("amount mapping format measurable " + am.getDouble());
				ti.setEnabled(true);
				return amountFormat.format(am.getDouble());
			} else {
				ti.setEnabled(false);
				return value.toString();
			}
		}

		@Override
		public Object valueOf(String text) {

			try {
				//System.out.println("amount mapping valueOf " + text);
				if (text.equals("t.t.") || text.equals("q.b.")) {
					//System.out.println("amount mapping valueOf verify " + text);
					return text;
				} else {
					//System.out.println("amount mapping valueOf not verify " + text);
					return nf.parse(text).doubleValue();
				}
			} catch (ParseException ex) {
				//Logger.getLogger(AmountTextInputMapping.class.getName()).log(Level.SEVERE, null, ex);
				return text;
			}
		}
	}

	private static class PercentTextInputMapping implements TextInput.TextBindMapping {

		private static NumberFormat percentFormat = NumberFormat.getPercentInstance();
		//private TextInput ti;

		//public PercentTextInputMapping(TextInput ti) {
		public PercentTextInputMapping() {
			percentFormat.setMaximumFractionDigits(2);
			//this.ti = ti;
		}

		@Override
		public String toString(Object value) {
			double percent = Double.valueOf(value.toString());
			if (percent > 0.0d) {
				//System.out.println("amount mapping format measurable " + am.getDouble());
				//ti.setEnabled(false);
				return percentFormat.format(percent);
			} else {
				//ti.setEnabled(false);
				return value.toString();
			}
		}

		@Override
		public Object valueOf(String text) {
			try {
				return percentFormat.parse(text).doubleValue();
			} catch (ParseException ex) {
				System.out.println("cannot parse percent");
				Logger.getLogger(FormulaTablePane.class.getName()).log(Level.SEVERE, null, ex);
				return text;
			}
		}
	}

	private static class FormulaRowEditor extends TableViewRowEditor {

		private static TextInput ti = new TextInput();
		private static TextInput bti = new TextInput();

		public FormulaRowEditor() {
			ti.setTextKey("Amount");
			ti.setTextBindMapping(new AmountTextInputMapping(ti));
			ti.getStyles().put("font", new Font("Verdana", Font.PLAIN, 12));
			// install the validator
			AmountValidator drvalidator = new AmountValidator(0.0d, 1e10);
			ti.setValidator(drvalidator);
			//ti.getTextInputContentListeners().add(ticl);
			this.getCellEditors().put("Amount", ti);

			// this is a fake editor used to avoid changing the
			// percent format while editing. There might be a better solution.
			bti.setTextKey("Percent");
			bti.setEnabled(false);
			bti.setTextBindMapping(new PercentTextInputMapping());
			bti.getStyles().put("font", new Font("Verdana", Font.PLAIN, 12));
			this.getCellEditors().put("Percent", bti);
			this.setEditEffect(SelectionChangeEffect.CROSSFADE);

		}

		@Override
		public boolean keyPressed(int keyCode, Keyboard.KeyLocation kl) {
			boolean consumed = false;
			// Change row only if the text is valid.
			// The endEdit() method calls store()
			// which gives an error is the text inserted in not valid.
			if (((TextInput) getCellEditors().get("Amount")).isTextValid()) {
				if (keyCode == Keyboard.KeyCode.ENTER) {
					endEdit(true);
					consumed = true;
				} else if (keyCode == Keyboard.KeyCode.ESCAPE) {
					endEdit(false);
					consumed = true;
				}
			}
			return consumed;
		}

		@Override
		public void endEdit(boolean result) {
			boolean valid = true;
			if (result) {
				Dictionary<String, Component> editors = getCellEditors();
				TableView.ColumnSequence cols = getTableView().getColumns();
				for (int i = 0, n = cols.getLength(); i < n; i++) {
					Component comp = editors.get(cols.get(i).getName());
					if (comp instanceof TextInput) {
						valid &= ((TextInput) comp).isTextValid();
					}
				}
			}
			if (!(result && !valid)) {
				super.endEdit(result);
			}
		}

	}

	private static class FormulaAmountLabelMapping implements Label.TextBindMapping {

		private static DecimalFormat amountFormat = (DecimalFormat) NumberFormat.getInstance();
		//private NumberFormat amountFormat = NumberFormat.getInstance(Locale.ITALIAN);;

		public String toString(Object o) {
			//System.out.println("formula tot amount "+o);
			amountFormat.setMinimumFractionDigits(1);
			amountFormat.setMaximumFractionDigits(1);
			return amountFormat.format(o);

		}

		public Object valueOf(String string) {
			throw new UnsupportedOperationException("Not supported yet.");
		}
	}

}